<?php

abstract class RunTimeWorkflow implements Serializable
{
  
  /**
   * @var array
   */
  protected $options = array();
  
  /**
   * @var AbstractWorkflowContainer
   */
  protected $container;
  
  /**
   * @var wfWorkflowInstance
   */
  protected $object;
  
  /**
   * @var DesignTimeWorkflow
   */
  protected $design_time;

  /**
   * Examples
   * <code>
   * __construct(AbstractWorkflowContainer $values, array $options)
   * __construct(wfWorkflowInstance $wfi)
   * </code>
   * 
   * @param mixed $param1
   * @param mixed $param2
   */
  public function __construct ($param1, $param2 = array())
  {
    if ($param1 instanceof AbstractWorkflowContainer && (is_null($param2) || is_array($param2)))
    {
      $this->__constructFromContainerAndOptions($param1, $param2);
    }
    elseif ($param1 instanceof wfWorkflowInstance)
    {
      $this->__constructFromWorkflowInstance($param1);
    }
    else
    {
      throw new InvalidArgumentException(sprintf('Invalid argument combination, $param1 with "%s" and $param2 with "%s"', $param1, $param2));
    }
  }

  /**
   * Called when the workflow is first started
   * 
   * @param AbstractWorkflowContainer $container
   * @param array $array
   */
  protected function __constructFromContainerAndOptions (AbstractWorkflowContainer $container, array $options = array())
  {
    $this->container = $container;
    $this->options = $options;
    
    $this->configure();
  }

  /**
   * Called when the workflow is resumed from the database
   * 
   * @param wfWorkflowInstance $instance
   */
  protected function __constructFromWorkflowInstance (wfWorkflowInstance $instance)
  {
    $this->unserialize($instance->getSerializedData());
    $this->resume();
  }

  protected function configure ()
  {
  
  }

  /**
   * @param string $key
   */
  public function hasOption ($key)
  {
    return isset($this->options[$key]);
  }

  /**
   * @param string $key
   * @param mixed $default
   */
  public function getOption ($key, $default = null)
  {
    if ($this->hasOption($key))
    {
      return $this->options[$key];
    }
    else
    {
      return $default;
    }
  }

  /**
   * @param string $key
   * @param mixed $value
   */
  public function setOption ($key, $value)
  {
    $this->options[$key] = $value;
  }

  /**
   * @param string $key
   * @param mixed $value
   */
  public function setDefaultOption ($key, $value)
  {
    if (! $this->hasOption($key))
    {
      $this->setOption($value);
    }
  }

  /**
   * Start workflow
   */
  public final function start ()
  {
    if ($this->canBeStarted())
    {
      $this->doStart();
    }
  }

  /**
   * @return bool
   */
  protected function canBeStarted ()
  {
    if (! is_null($this->object) && ! $this->object->isNew())
    {
      throw new WorkflowRunTimeException('Workflow already started.');
    }
    elseif (! $this->design_time->validate())
    {
      throw new WorkflowRunTimeException('Validation failed.');
    }
    
    return true;
  }

  protected function doStart ()
  {
    if (is_null($this->object))
    {
      $this->object = new wfWorkflowInstance();
    }
    $this->save();
    
    $this->design_time->getStartNode()
      ->createRunTimeNode($this)
      ->deliver();
  
  }

  /**
   * Resume workflow
   */
  protected final function resume ()
  {
    if ($this->canBeResumed())
    {
      $this->doResume();
    }
  }

  /**
   * @return bool
   */
  protected function canBeResumed ()
  {
    return true;
  }

  protected function doResume ()
  {
    // TODO Implement
  }

  public final function suspend ()
  {
    if ($this->canBeSuspended())
    {
      $this->doSuspend();
    }
  }

  /**
   * @return bool
   */
  protected function canBeSuspended ()
  {
    return true;
  }

  protected function doSuspend ()
  {
  
  }

  /**
   * @throws WorkflowRunTimeException
   */
  public final function complete ()
  {
    if ($this->canBeCompleted())
    {
      $this->doComplete();
    }
  }

  /**
   * @return bool
   * @throws WorkflowRunTimeException
   */
  protected function canBeCompleted ()
  {
    if (is_null($this->object))
    {
      throw new WorkflowRunTimeException('Workflow not started');
    }
    elseif ($this->object->isCompleted())
    {
      throw new WorkflowRunTimeException('Workflow already completed.');
    }
    return true;
  }

  protected function doComplete ()
  {
    $this->object->setCompletedAt(date('Y-m-d H:i:s'));
    $this->save();
  }

  /**
   * @param string $id
   */
  public function handleWorkItemCompleted ($id)
  {
  
  }

  public function handleContainerUpdated ()
  {
    $this->save();
  }

  /**
   * @param string $id
   */
  public function handleWorkItemDelivered ($id)
  {
  
  }

  /**
   * @param string $id
   */
  public function handleWorkItemExecuted ($id)
  {
    // TODO Save container
  }

  protected function save ()
  {
    $this->object->setClass(get_called_class());
    $this->object->setSerializedData(serialize($this));
    $this->object->save();
  }

  /**
   * @return wfWorkflowInstance
   */
  public function getObject ()
  {
    return $this->object;
  }

  /**
   * @return DesignTimeWorkflow
   */
  public function getDesignTime ()
  {
    if (is_null($this->design_time))
    {
      $this->design_time = new DesignTimeWorkflow();
    }
    return $this->design_time;
  }

  public function getContainer ()
  {
    return $this->container;
  }

  public function serialize ()
  {
    $data = array(
      'container' => $this->container, 'design_time' => $this->design_time, 'options' => $this->options);
    return serialize($data);
  }

  public function unserialize ($serialized)
  {
    $data = unserialize($serialized);
    $this->container = $data['container'];
    $this->design_time = $data['design_time'];
    $this->options = $data['options'];
  }
}